module Cache #(
    parameter ADDR_WIDTH    = 32,
    parameter DATA_WIDTH    = 64,
    parameter MASK_WIDTH    = DATA_WIDTH / 8,

    parameter OFFSET_WIDTH  = 4,
    parameter INDEX_WIDTH   = 7,
    parameter TAG_WIDTH     = 21,

    parameter LINE_WIDTH    = 128,
    parameter STRB_WIDTH    = LINE_WIDTH / 8
) (
    input   logic                   clock,
    input   logic                   reset,
/* ********* Cache <-> CPU ********* */
    input   logic                   req_valid,
    input   logic                   op,     // 1: WRITE, 0: READ
    input   logic [TAG_WIDTH-1:0]   tag,
    input   logic [INDEX_WIDTH-1:0] index,
    input   logic [OFFSET_WIDTH-1:0]offset,
    input   logic [MASK_WIDTH-1:0]  wstrb,
    input   logic [DATA_WIDTH-1:0]  wdata,
    output  logic                   addr_ok,
    output  logic                   data_ok,
    output  logic [DATA_WIDTH-1:0]  rdata,
    input   logic [2:0]             size,
    input   logic                   uncache,
    output  logic                   cache_miss,
    output  logic                   load_fault,
    output  logic                   store_fault,
/* ********* Cache <-> CPU ********* */

/* ********* Cache <-> AXI ********* */
    // AXI Read
    output  logic                   rd_req,
    output  logic [2:0]             rd_type,
    output  logic [ADDR_WIDTH-1:0]  rd_addr,
    input   logic                   rd_rdy,
    input   logic                   ret_valid,
    input   logic                   ret_last,
    input   logic [LINE_WIDTH-1:0]  ret_data,
    input                           read_fault,

    // AXI Write
    output  logic                   wr_req,
    output  logic [2:0]             wr_type,
    output  logic [ADDR_WIDTH-1:0]  wr_addr,
    output  logic [STRB_WIDTH-1:0]  wr_wstrb,
    output  logic [LINE_WIDTH-1:0]  wr_data,
    input   logic                   wr_rdy,
    input                           write_fault
/* ********* Cache <-> AXI ********* */
);
    assign load_fault   = read_fault;
    assign store_fault  = write_fault;
/* ------------------------ Array ------------------------ */
    wire need_refill = state_refill2idle & ~uncache_R;

    // Tag Array
    reg [TAG_WIDTH-1:0] tag_array [1:0] [LINE_WIDTH-1:0];
    always_ff @(posedge clock) begin
        if (reset) begin
            tag_array[0]   <= '{128{21'b0}};
            tag_array[1]   <= '{128{21'b0}};
        end
        // 更新待替换 Way 的 Tag
        else if (need_refill) begin
            tag_array[replace_way][index_R] <= tag_R;
        end
    end

    // Valid Array
    reg [LINE_WIDTH-1:0] valid_array [1:0];
    always_ff @(posedge clock) begin
        if (reset) begin
            valid_array[0]   <= {128{1'b0}};
            valid_array[1]   <= {128{1'b0}};
        end
        // 更新待替换 Way 的 Valid
        else if (need_refill) begin
            valid_array[replace_way][index_R] <= 1'b1;
        end
    end

    // Dirty Array
    reg [LINE_WIDTH-1:0] dirty_array [1:0];
    always_ff @(posedge clock) begin
        if (reset) begin
            dirty_array[0]   <= {128{1'b0}};
            dirty_array[1]   <= {128{1'b0}};
        end
        else if (recv_new_req & hit_write) begin
            // !!!!!!!!!!!!!!!!!!!
            dirty_array[hit_way][index] <= 1'b1;
        end
        else if (need_refill & op_R) begin
            dirty_array[replace_way][index_R] <= 1'b1;
        end
    end
/* ------------------------ Array ------------------------ */

/* ------------------------ Hit Information ------------------------ */
    wire  can_recv_req = (state_is_idle) | (state_is_lookup & cache_hit_R);
    wire  recv_new_req = can_recv_req & req_valid;

    logic [TAG_WIDTH-1:0]   way_tag [1:0];
    assign way_tag[0] = tag_array[0][index];
    assign way_tag[1] = tag_array[1][index];

    logic                   way_valid [1:0];
    assign way_valid[0] = valid_array[0][index];
    assign way_valid[1] = valid_array[1][index];

    logic                   way_dirty [1:0];
    assign way_dirty[0] = dirty_array[0][index];
    assign way_dirty[1] = dirty_array[1][index];

    wire                   way_hit [1:0];
    assign way_hit[0]   = way_valid[0] & (way_tag[0] == tag) & recv_new_req & ~uncache;
    assign way_hit[1]   = way_valid[1] & (way_tag[1] == tag) & recv_new_req & ~uncache;

    wire    hit_way   = way_hit[1];
    wire    cache_hit = way_hit[0] | way_hit[1];
    wire    hit_write = cache_hit & op;

    logic [TAG_WIDTH-1:0]   way_tag_R [1:0];
    logic                   way_valid_R [1:0];
    logic                   way_dirty_R [1:0];
    logic                   way_hit_R [1:0];
    logic                   cache_hit_R;
    logic                   op_R;
    logic [MASK_WIDTH-1:0]  wstrb_R;
    logic [DATA_WIDTH-1:0]  wdata_R;
    logic [TAG_WIDTH-1:0]   tag_R;
    logic [INDEX_WIDTH-1:0] index_R;
    logic [OFFSET_WIDTH-1:0]offset_R;
    logic [2:0]             size_R;
    logic                   uncache_R;

    always_ff @(posedge clock) begin
        if (reset) begin
            way_tag_R   <=  '{2{21'b0}};
            way_valid_R <=  '{2{1'b0}};
            way_dirty_R <=  '{2{1'b0}};
            way_hit_R   <=  '{2{1'b0}};
            {
                cache_hit_R, op_R, wstrb_R, wdata_R,
                tag_R, index_R, offset_R, size_R, uncache_R
            } <= 0;
        end
        else if (recv_new_req) begin
            way_tag_R   <=  way_tag;
            way_valid_R <=  way_valid;
            way_dirty_R <=  way_dirty;
            way_hit_R   <=  way_hit;
            {
                cache_hit_R, op_R, wstrb_R, wdata_R,
                tag_R, index_R, offset_R, size_R, uncache_R
            } <=
            {
                cache_hit, op, wstrb, wdata,
                tag, index, offset, size, uncache
            };
        end
    end
/* ------------------------ Hit Information ------------------------ */

/* ------------------------ LFSR ------------------------ */
    // 三级伽罗瓦 LFSR
    logic [2:0] lfsr;
    always_ff @(posedge clock) begin
        if (reset) begin
            lfsr <= 3'b111;
        end
        // 在 REFILL -> IDLE 时变化
        else if (state_refill2idle) begin
            lfsr <= { lfsr[0], lfsr[2] ^ lfsr[0], lfsr[1] };
        end
    end
    wire chosen_way = lfsr[0];

    // 解释: 若 way0_v_R 与 way1_v_R 为不同值(其中一个为 0, 另一个为 1), 优先选择为 0 的一路;
    //       若 way0_d_R 与 way1_d_R 为不同值(其中一个为 0, 另一个为 1), 优先选择为 0 的一路;
    wire replace_way =  (way_valid_R[0] ^ way_valid_R[1]) ? (way_valid_R[0]) :
                        (way_dirty_R[0] ^ way_dirty_R[1]) ? (way_dirty_R[0]) :
                        chosen_way;
    wire need_replace = ~uncache_R & way_dirty_R[replace_way] & way_valid_R[replace_way];
    wire [TAG_WIDTH-1:0] replace_tag = way_tag_R[replace_way];
    // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    // 由于 SRAM 只会在 IDLE 或 LOOKUP 时候读, 故需要将要替换的数据保存下来, 否则后期读出的数据不正确
    logic [LINE_WIDTH-1:0] replace_data;
    always_ff @(posedge clock) begin
        if (reset) begin
            replace_data <= 0;
        end
        else if (state_is_lookup & need_replace) begin
            replace_data <= way_rdata[replace_way];
        end
    end
/* ------------------------ LFSR ------------------------ */

/* ------------------- Cache <-> CPU ------------------- */
    assign addr_ok = can_recv_req;
    assign data_ok = (state_is_lookup & cache_hit_R)
                   | (state_refill2idle)
                   | write_fault
                   | read_fault;
    wire [LINE_WIDTH-1:0] load_line = way_rdata[way_hit_R[1]];
    wire [DATA_WIDTH-1:0] load_data = offset_R[3] ? load_line[127:64] : load_line[63:0];
    // uncache_R ? ret_data[63:0] : offset_R[3] ? ret_data[127:64] : ret_data[63:0]
    wire [DATA_WIDTH-1:0] read_data = (uncache_R | ~offset_R[3]) ? ret_data[63:0] : ret_data[127:64];

    assign rdata = {64{state_is_lookup}} & load_data
                 | {64{state_is_refill}} & read_data;

    assign cache_miss = state_refill2idle & ~uncache_R;
/* ------------------- Cache <-> CPU ------------------- */

/* ------------------------ FSM ------------------------ */
    wire uncache_write = uncache_R & op_R;

    localparam IDLE     = 5'b00001;
    localparam LOOKUP   = 5'b00010;
    localparam MISS     = 5'b00100;
    localparam REPLACE  = 5'b01000;
    localparam REFILL   = 5'b10000;

    wire  state_is_idle    = (curr_state == IDLE);
    wire  state_is_lookup  = (curr_state == LOOKUP);
    wire  state_is_miss    = (curr_state == MISS);
    wire  state_is_replace = (curr_state == REPLACE);
    wire  state_is_refill  = (curr_state == REFILL);

    wire  state_refill2idle = state_is_refill & ((ret_valid & ret_last) | (~rd_req_R));

    logic [4:0] curr_state, next_state;

    always_ff @(posedge clock) begin
        if (reset) begin
            curr_state <= IDLE;
        end
        else begin
            curr_state <= next_state;
        end
    end

    always_comb begin
        case (curr_state)
            IDLE: begin
                if (req_valid) begin
                    next_state = LOOKUP;
                end
                else begin
                    next_state = IDLE;
                end
            end
            LOOKUP: begin
                // Hit
                if (cache_hit_R) begin
                    if (~req_valid) next_state = IDLE;      // no  request
                    else            next_state = LOOKUP;    // new request
                end
                // Uncache Write & Cache Miss
                else if (uncache_write | need_replace) begin
                    next_state = MISS;
                end
                // Uncache Read
                else begin
                    next_state = REPLACE; // 不需要发起 AXI Write
                end
            end
            MISS: begin
                if (wr_rdy) begin
                    // Uncache Write 不需要发起 AXI Read
                    if (uncache_write)  next_state = REFILL;
                    else                next_state = REPLACE;
                end
                else begin
                    next_state = MISS;
                end
            end
            REPLACE: begin
                // 写异常处理
                if (write_fault) begin
                    next_state = IDLE;
                end
                else if (rd_rdy) begin
                    next_state = REFILL;
                end
                else begin
                    next_state = REPLACE;
                end
            end
            REFILL: begin
                // 读异常处理
                if (read_fault) begin
                    next_state = IDLE;
                end
                // Uncache Write 没有发起 AXI Read
                else if ((ret_valid & ret_last) | (~rd_req_R)) begin
                    next_state = IDLE;
                end
                else begin
                    next_state = REFILL;
                end
            end
            default: begin
                next_state = IDLE;
            end
        endcase
    end
/* ------------------------ FSM ------------------------ */

/* ------------------------ Cache <-> AXI ------------------------ */
    logic wr_req_R;
    assign wr_req   = wr_req_R;
    always_ff @(posedge clock) begin
        if (reset) begin
            wr_req_R <= 1'b0;
        end
        // 当前处于 MISS->REPLACE 状态时将 wr_req_R 置为 1
        else if (state_is_miss & wr_rdy) begin
            wr_req_R <= 1'b1;
        end
        else begin
            wr_req_R <= 1'b0;
        end
    end
    // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    assign wr_type  = uncache_R ? size_R : 3'b100;
    assign wr_addr  = ({ 32{ uncache_R}} & {tag_R, index_R, offset_R})
                    | ({ 32{~uncache_R}} & {replace_tag, index_R, 4'h0}); 
    assign wr_data  = ({128{ uncache_R}} & {64'h0, wdata_R})
                    | ({128{~uncache_R}} & {replace_data}); // !!!!!!
    assign wr_wstrb = ({ 16{ uncache_R}} & {8'h0, wstrb_R})
                    | ({ 16{~uncache_R}} & 16'hFFFF); // !!!!!!!!!!!!

    logic rd_req_R;
    // Uncache Write 不需要发起 AXI Read
    assign rd_req   = state_is_replace;
    always_ff @(posedge clock) begin
        if (reset) begin
            rd_req_R <= 1'b0;
        end
        else if (rd_req) begin
            rd_req_R <= 1'b1;
        end
        else if (state_is_refill & ret_valid & ret_last) begin
            rd_req_R <= 1'b0;
        end
    end
    // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    assign rd_type  = uncache_R ? size_R : 3'b100;
    assign rd_addr  = { tag_R, index_R, uncache_R ? offset_R : 4'h0 };
/* ------------------------ Cache <-> AXI ------------------------ */

/* --------------------------- SRAM --------------------------- */
    logic [LINE_WIDTH-1:0] way_rdata [1:0];
    assign way_rdata[0] = ~index_msb_R ? way0_bank0_rdata : way0_bank1_rdata;
    assign way_rdata[1] = ~index_msb_R ? way1_bank0_rdata : way1_bank1_rdata;

    wire [63:0]     mask_hit = {
        {8{wstrb[7]}}, {8{wstrb[6]}}, {8{wstrb[5]}}, {8{wstrb[4]}},
        {8{wstrb[3]}}, {8{wstrb[2]}}, {8{wstrb[1]}}, {8{wstrb[0]}}
    };
    wire [LINE_WIDTH-1:0]   bwen_hit  = offset[3] ? {mask_hit, 64'h0} : {64'h0, mask_hit};

    wire [LINE_WIDTH-1:0]   sram_bwen = ({LINE_WIDTH{recv_new_req   }} & bwen_hit )
                                      | ({LINE_WIDTH{state_is_refill}} & {LINE_WIDTH{1'b1}});

    logic                   index_msb;
    logic [INDEX_WIDTH-2:0] index_addr;
    assign { index_msb, index_addr } = index;
    logic                   index_msb_R;
    logic [INDEX_WIDTH-2:0] index_addr_R;
    assign { index_msb_R, index_addr_R } = index_R;

    wire [INDEX_WIDTH-2:0]  sram_addr = ({6{recv_new_req   }} & index_addr)
                                      | ({6{state_is_refill}} & index_addr_R);

    wire [DATA_WIDTH-1:0]   mask_miss = {
        {8{wstrb_R[7]}}, {8{wstrb_R[6]}}, {8{wstrb_R[5]}}, {8{wstrb_R[4]}},
        {8{wstrb_R[3]}}, {8{wstrb_R[2]}}, {8{wstrb_R[1]}}, {8{wstrb_R[0]}}
    };
    wire [LINE_WIDTH-1:0]   bwen_miss = offset_R[3] ? {mask_miss, 64'h0} : {64'h0, mask_miss};
    wire [LINE_WIDTH-1:0]   wdata_ext = offset_R[3] ? {wdata_R,   64'h0} : {64'h0, wdata_R  };
    // 应该分别考虑读写
    wire [LINE_WIDTH-1:0]   wdata_mix  = (wdata_ext & bwen_miss) | (ret_data & ~bwen_miss);
    wire [LINE_WIDTH-1:0]   wdata_miss = ({LINE_WIDTH{ op_R}} & wdata_mix)
                                       | ({LINE_WIDTH{~op_R}} & ret_data);

    wire [LINE_WIDTH-1:0]   wdata_hit  = offset[3] ? {wdata, 64'h0}: {64'h0, wdata};

    wire [LINE_WIDTH-1:0]   sram_wdata = ({LINE_WIDTH{recv_new_req   }} & wdata_hit)
                                       | ({LINE_WIDTH{state_is_refill}} & wdata_miss);
    /* ===================== Way0 ===================== */
    wire way0_bank0_ren = (recv_new_req & ~index_msb);
    wire way0_bank0_wen = (recv_new_req & ~index_msb & way_hit[0] & op)
                        | (need_refill  & ~index_msb_R & ~replace_way);
    wire way0_bank0_cen = way0_bank0_ren | way0_bank0_wen;
    logic [LINE_WIDTH-1:0] way0_bank0_rdata;
    SRAM way0_bank0(
        .CLK        (clock),
        .CEN        (~way0_bank0_cen),
        .WEN        (~way0_bank0_wen),
        .BWEN       (~sram_bwen),
        .A          (sram_addr),
        .Q          (way0_bank0_rdata),
        .D          (sram_wdata)
    );

    wire way0_bank1_ren = (recv_new_req &  index_msb);
    wire way0_bank1_wen = (recv_new_req & index_msb & way_hit[0] & op)
                        | (need_refill  & index_msb_R & ~replace_way);
    wire way0_bank1_cen = way0_bank1_ren | way0_bank1_wen;
    logic [LINE_WIDTH-1:0] way0_bank1_rdata;
    SRAM way0_bank1(
        .CLK        (clock),
        .CEN        (~way0_bank1_cen),
        .WEN        (~way0_bank1_wen),
        .BWEN       (~sram_bwen),
        .A          (sram_addr),
        .Q          (way0_bank1_rdata),
        .D          (sram_wdata)
    );
    /* ===================== Way0 ===================== */

    /* ===================== Way1 ===================== */
    wire way1_bank0_ren = (recv_new_req & ~index_msb);
    wire way1_bank0_wen = (recv_new_req & ~index_msb & way_hit[1] & op)
                        | (need_refill  & ~index_msb_R & replace_way);
    wire way1_bank0_cen = way1_bank0_ren | way1_bank0_wen;
    logic [LINE_WIDTH-1:0] way1_bank0_rdata;
    SRAM way1_bank0(
        .CLK        (clock),
        .CEN        (~way1_bank0_cen),
        .WEN        (~way1_bank0_wen),
        .BWEN       (~sram_bwen),
        .A          (sram_addr),
        .Q          (way1_bank0_rdata),
        .D          (sram_wdata)
    );

    wire way1_bank1_ren = (recv_new_req &  index_msb);
    wire way1_bank1_wen = (recv_new_req & index_msb & way_hit[1] & op)
                        | (need_refill  & index_msb_R & replace_way);
    wire way1_bank1_cen = way1_bank1_ren | way1_bank1_wen;
    logic [LINE_WIDTH-1:0] way1_bank1_rdata;
    SRAM way1_bank1(
        .CLK        (clock),
        .CEN        (~way1_bank1_cen),
        .WEN        (~way1_bank1_wen),
        .BWEN       (~sram_bwen),
        .A          (sram_addr),
        .Q          (way1_bank1_rdata),
        .D          (sram_wdata)
    );
    /* ===================== Way1 ===================== */

/* --------------------------- SRAM --------------------------- */

endmodule //Cache
